[前編] AWS CDKで API Gateway + Lambda 構成のREST APIを構築して Auth0 + Lambda Authorizerの認可機能を導入してみた
CX事業本部Delivery部のアベシです。
こちらの記事では、API Gateway + Lambda のREST APIに Auth0 + Lambda Authorizerの認可を導入する方法について紹介します。
前編、更編に分けて紹介します。
今回の前編ではLambda Authorizer と Auth0を使ったAPI Gatewayの保護の仕組みと、土台となるAPIのCDKコードについて紹介しようと思います。
Lambda Authorizer と Auth0を使った認可の仕組み
以下のフローで認可が行われます。
① クライアントがAuth0に認可をリクエストする。
② 認可されたらAuth0がアクセストークンを返す。
③ クライアントがAPIコールする。その際にアクセストークンをヘッダーとしてAPI Gatewayに渡す。
④ API GatewayがLambda Authorizerにアクセストークンを渡して関数を実行する。
⑤ Lambda Authorizerはアクセストークンを用いてAuth0へ公開鍵の取得をリクエストする。
⑥ Auth0が公開鍵をLambda Authorizerに返し、アクセストークンの RS256 署名の検証を実行する。
⑦ 検証が成功したらAPIを実行するために必要なポリシーを作成しAPI Gatewayに渡す。
⑧ API GatewayがLambda関数を実行する。
上で解説した内容はAuth0の以下の公式記事にも詳しく書かれてますので、参照していただければと思います。
実行環境
以下の環境で構築と動作確認しています。
項目名 | バージョン |
---|---|
mac OS | Ventura 13.2 |
npm | 9.6.0 |
AWS CDK | 2.66.1 |
API Gateway + Lambda の REST API
まずは土台となるREST APIのコードが以下となります。
Lambda統合プロキシ
を用いたのREST APIとなります。
import { Stack, StackProps, aws_apigateway, aws_lambda_nodejs, Duration } from 'aws-cdk-lib'; import { Runtime } from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; export class LambdaAuthorizerWithAuth0Stack extends Stack { constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); //HelloWorldするLambdaの作成 const nameHelloWorldFunc = "Hello_world_func" // 名前にspaceが使えないので注意 const registerTaskFunc = new aws_lambda_nodejs.NodejsFunction( this, nameHelloWorldFunc, { runtime: Runtime.NODEJS_18_X, functionName: nameHelloWorldFunc, entry: 'src/lambda/handlers/hello-world-func.ts', timeout: Duration.seconds(25), logRetention: 30, }, ); // API Gateway RestAPIの作成 const nameRestApi ="Rest API with Lambda auth"; const restApi = new aws_apigateway.RestApi(this, nameRestApi, { restApiName: `Rest_API_with_Lambda_auth`, deployOptions: { stageName: 'v1', }, }); //API Gatewayにリクエスト先のリソースを追加 const restApiHelloWorld = restApi.root.addResource('hello_world'); //リソースにGETメソッド、Lambda統合プロキシを指定 restApiHelloWorld.addMethod( 'GET', new aws_apigateway.LambdaIntegration(registerTaskFunc) ); } }
コードの解説
- Lambdaのビルド方法定義
API Gatewayの後続のLambda関数のビルド方法の定義します。
aws_lambda_nodejsのNodejsFunction
クラスを使用してビルドします。
ランタイムはNode.js18です(CDKのバージョンが古いと指定できません)。Node.jsはバージョン16が2023年9月11日でサポート終了なのできれば18を使いましょう。
あと、functionNameプロパティですが、名前にspaceが使えないです。私はよく忘れてひっかかってしまってます。
Bringing forward the End-of-Life Date for Node.js 16
- Rest APIの作成
aws_apigatewayのRestApi
クラスを使って定義します。
LambdaとのリクエストメッセージとレスポンスのやりとりにはLambda統合プロキシ
を使用しています。
メソッドの定義のところでLambdaIntegrationクラスを使用して定義しています。
restApiHelloWorld.addMethod( 'GET', new aws_apigateway.LambdaIntegration(registerTaskFunc) );
Lambdaからのレスポンスは決められたJSON形式で返す必要があります。
レスポンスに使えるプロパティは以下となります。
{ "isBase64Encoded": true|false, "statusCode": httpStatusCode, "headers": { "headerName": "headerValue", ... }, "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... }, "body": "..." }
この内必須項目はstatusCode
のみとなります。
Lambda統合プロキシ
を使用しない場合はLambdaへリクエストメッセージのどの部分を渡すのか、Lambdaからのレスポンスにどんな項目を返すのかを指定するために、モデルとマッピングテンプレートの定義が必要です。
実際にどちらも試すとLambda統合プロキシ
のほうが如何に楽にREST APIが作れるか解ると思います。
デプロイ
$ cdk deploy --require-approval never '*' Bundling asset LambdaAuthorizerWithAuth0Stack/Hello_world_func/Code/Stage... ... Outputs: LambdaAuthorizerWithAuth0Stack.RestAPIEndpointB14C3C54 = https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/
AWS CLIでデプロイするとAPI GatewayのURLが表示されますので控えておきます。
curl コマンドでAPIを叩いてみましょう。
curl -X GET \ https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/hello_world
出力
Hello World!!
CDKのコードにLambda Authorizerを追加
先程のコードにLambda Authorizerの導入に必要な定義を追加します。
追加後のコードが以下となります。
import { Stack, StackProps, aws_apigateway, aws_lambda_nodejs, aws_iam, Duration, } from 'aws-cdk-lib'; import { Runtime } from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; export class LambdaAuthorizerWithAuth0Stack extends Stack { constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); const nameHelloWorldFunc = "Hello_world_func" const registerTaskFunc = new aws_lambda_nodejs.NodejsFunction( this, nameHelloWorldFunc, { runtime: Runtime.NODEJS_18_X, functionName: nameHelloWorldFunc, entry: 'src/lambda/handlers/hello-world-func.ts', timeout: Duration.seconds(25), logRetention: 30, }, ); // Lambda Authorizer用のLambda関数 const nameLambdaAuthorizerFunc = "Lambda_Authorizer_Function" const lambdaAuthorizerFunc = new aws_lambda_nodejs.NodejsFunction( this, nameLambdaAuthorizerFunc, { functionName: nameLambdaAuthorizerFunc, entry: 'src/lambda/handlers/lambda-authorizer.ts', runtime: Runtime.NODEJS_18_X, timeout: Duration.seconds(25), logRetention: 30, environment: { //トークンの検証に必要なデータを環境変数に格納 AUDIENCE: "https://**********.execute-api.ap-northeast-1.amazonaws.com", JWKS_URI: "https://your_tenant.auth0.com/.well-known/jwks.json", TOKEN_ISSUER: "https://your_tenant.auth0.com/" }, }, ); const restApi = new aws_apigateway.RestApi(this, 'RestAPI', { restApiName: `restApi`, deployOptions: { stageName: 'v1', }, }); // Lambda Authorizerの定義 const lambdaAuth = new aws_apigateway.TokenAuthorizer( this, 'lambdaAuthorizer', { authorizerName: 'lambdaAuthorizer', handler: lambdaAuthorizerFunc,//ここでLambda Authorizer用のLambda関数を割り当てる identitySource: aws_apigateway.IdentitySource.header('Authorization'),//アクセストークンを渡すためのヘッダーを指定 }, ); const restApiTasks = restApi.root.addResource('hello_world'); restApiTasks.addMethod( 'GET', new aws_apigateway.LambdaIntegration(registerTaskFunc), { authorizer: lambdaAuth, // 定義したLambdaAuthorizerを指定 }, ); } }
コードの解説
追加したコードについて説明します。
- Lambda Authorizer用のLambda関数のビルド定義
こちらもNodejsFunctionクラスつかったビルドを定義しています。
アクセストークンのRS256署名を検証するの際に使う値を環境変数に指定してます。
この変数については後編で説明します。
const lambdaAuthorizerFunc = new aws_lambda_nodejs.NodejsFunction( this, nameLambdaAuthorizerFunc, { functionName: nameLambdaAuthorizerFunc, entry: 'src/lambda/handlers/lambda-authorizer.ts', runtime: Runtime.NODEJS_18_X, timeout: Duration.seconds(25), logRetention: 30, environment: { //トークンの検証に必要なデータを環境変数に格納 AUDIENCE: "https://**********.execute-api.ap-northeast-1.amazonaws.com", JWKS_URI: "https://your_tenant.auth0.com/.well-known/jwks.json", TOKEN_ISSUER: "https://your_tenant.auth0.com/" }, }, );
- Lambda Authorizerの定義
TokenAuthorizerクラスを使用してAPI Gatewayの認可にLambda Authorizerを使用するように定義しています。
handlerプロパティに前段で作成したLambda Authorizer用のLambda関数を割り当てます。
identitySourceプロパティにはどのヘッダーにアクセストークンを持たせるか指定します。
ここではAuthorization
ヘッダーを指定しています。
const nameLambdaAuth = 'lambdaAuthorizer' const lambdaAuth = new aws_apigateway.TokenAuthorizer( this, nameLambdaAuth, { authorizerName: nameLambdaAuth, handler: lambdaAuthorizerFunc,// ここでLambda Authorizer用のLambda関数を割り当てる identitySource: aws_apigateway.IdentitySource.header('Authorization'), }, );
- メソッドに認可方法を指定
前段でTokenAuthorizerクラスを使って定義したLambda Authorizer
をauthorizer
プロパティに指定しています。
restApiTasks.addMethod( 'GET', new aws_apigateway.LambdaIntegration(registerTaskFunc), { authorizer: lambdaAuth, // 定義したLambda Authorizerを指定 }, );
CDKのコードについての解説は以上です。
後編で紹介する内容
次回の後編では以下を紹介いたします。
- Auth0側の設定
- Lambda AuthorizerのLambda関数の紹介と解説
- 認可の動作確認
前編は以上、後編に続く。。。
後編
以下のブログが後編となります。